package pers.jason.wppm.security.core.validate.code.processor.impl;

import java.io.IOException;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import pers.jason.wppm.security.core.properties.SecurityProperties;
import pers.jason.wppm.security.core.validate.code.ValidateCode;
import pers.jason.wppm.security.core.validate.code.exception.ValidateCodeException;
import pers.jason.wppm.security.core.validate.code.generator.ValidateCodeGenerator;
import pers.jason.wppm.security.core.validate.code.processor.CodeCheckable;
import pers.jason.wppm.security.core.validate.code.processor.ValidateCodeProcessor;

/**
 * 校验码
 * @author Jason
 * @Date 2018-10-31 10:56
 *
 */
public abstract class AbstractValidateCodeTemplate <T extends ValidateCode> implements ValidateCodeProcessor, CodeCheckable {
	
	private static final String sessionKeyPrefix = "VALIDATE_CODE_IN_SESSION_";
	
	private Logger logger = LoggerFactory.getLogger(getClass());

	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
	
	@Autowired
	private Map<String, ValidateCodeGenerator> generators;

	@Autowired
	private SecurityProperties securityProperties;
	
	@Override
	public final void responseValidateCode(ServletWebRequest request) throws Exception {
		T validateCode = createValidateCode(request);
		logger.info("校验码内容：" + validateCode.getCode());
		setCodeInSession(request, validateCode);
		responseValidateCode(request, validateCode);
		logger.info("返回校验码...");
	}
	
	@Override
	public void validateCode(ServletWebRequest request) throws ValidateCodeException {
		// TODO Auto-generated method stub
		logger.info("开始校验验证码...");
		String codeType = getCodeType();
		String sessionKey = getSessionKey();
		@SuppressWarnings("unchecked")
		T codeInSession = (T) sessionStrategy.getAttribute(request, sessionKey);
		String codeInRequest = "";
		try {
			codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), getParamName());
		} catch (ServletRequestBindingException e) {
			throw new ValidateCodeException("获取验证码的值失败");
		}
		if (StringUtils.isBlank(codeInRequest)) {
			throw new ValidateCodeException("验证码的值不能为空");
		}
		if (codeInSession == null) {
			throw new ValidateCodeException("验证码不存在");
		}
		if (codeInSession.isExpried()) {
			sessionStrategy.removeAttribute(request, sessionKey);
			throw new ValidateCodeException("验证码已过期");
		}
		if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
			throw new ValidateCodeException("验证码不匹配");
		}
		logger.info("校验成功");
		sessionStrategy.removeAttribute(request, sessionKey);
	}
	
	
	
	
	
	
	
	
	
	
	/**
	 * 生成校验码对象
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private T createValidateCode(ServletWebRequest request) {
		String type = getCodeType();
		String name = type + "CodeGenerator";
		ValidateCodeGenerator generator = generators.get(name);
		T code = (T) generator.createValidateCode(request);
		return code;
	}
	
	/**
	 * 将校验码存储到session中
	 * @param request
	 * @param code
	 */
	protected void setCodeInSession(ServletWebRequest request, T code) {
		String sessionKey = getSessionKey();
		sessionStrategy.setAttribute(request, sessionKey, code);
	}
	
	/**
	 * 由基类使用，由子类实现
	 * @param request
	 * @param code
	 */
	protected abstract void responseValidateCode(ServletWebRequest request, T code)  throws IOException;
	
	private String getSessionKey() {
		String type = getCodeType();
		String key = sessionKeyPrefix + type;
		return key;
	}
	
	private String getCodeType() {
		return StringUtils.substringBefore(getClass().getSimpleName(), "ValidateCodeProcessor").toLowerCase();
	}

	public SecurityProperties getSecurityProperties() {
		return securityProperties;
	}

	public void setSecurityProperties(SecurityProperties securityProperties) {
		this.securityProperties = securityProperties;
	}
}
